/*
 * Copyright (c) 2008, Xiong Yingfei, the University of Tokyo All rights
 * reserved.
 */
package ac.jp.u_tokyo.SyncLib.language3;

import static ac.jp.u_tokyo.SyncLib.util.Helper.apply;
import static ac.jp.u_tokyo.SyncLib.util.Helper.conflict;
import static ac.jp.u_tokyo.SyncLib.util.Helper.inPlaceApply;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import ac.jp.u_tokyo.SyncLib.IDSync;
import ac.jp.u_tokyo.SyncLib.Mod;
import ac.jp.u_tokyo.SyncLib.ModTypeUnsupportedException;
import ac.jp.u_tokyo.SyncLib.NullMod;
import ac.jp.u_tokyo.SyncLib.PrimMod;
import ac.jp.u_tokyo.SyncLib.StateSync;
import ac.jp.u_tokyo.SyncLib.SynchronizationFailedException;
import ac.jp.u_tokyo.SyncLib.Value2Mod;
import ac.jp.u_tokyo.SyncLib.dictionaries.DictMod;
import ac.jp.u_tokyo.SyncLib.dictionaries.DictModFactory;
import ac.jp.u_tokyo.SyncLib.util.Helper;
import ac.jp.u_tokyo.SyncLib.util.TwoIterable;

public class FindBy extends StateSync
{
	Object _attributeName;

	public void setAttributeName(Object attributeName)
	{
		this._attributeName = attributeName;
	}

	public FindBy()
	{
		super(new Object[] { new HashMap(), null });
	}

	public FindBy(String attr)
	{
		super(new Object[] { new HashMap(), null });
		_attributeName = attr;
	}

	private Map<Integer, Map<Object, Object>> getDict()
	{
		return (Map<Integer, Map<Object, Object>>) getAttribute(0);
	}

	private void modifyDict(Mod<Map<Integer, Map<Object, Object>>> dict)
	{
		modifyAttribute(0, dict);
	}

	private void setKey(Integer key)
	{
		setAttribute(1, key);
	}

	private Integer getKey()
	{
		return (Integer) getAttribute(1);
	}

	public int getParaCount()
	{
		return 3;
	}

	public Mod[] resynchronize(Object[] values, Mod[] modifications)
			throws SynchronizationFailedException
	{
		Mod[] result = internalSync(modifications, (Integer) values[2],
				(Map<Integer, Map<Object, Object>>) values[0], values[1]);

		if (values[0] == null && result[0] instanceof NullMod)
		{
			result[0] = NULLDICTMOD;
		}

		setKey(apply((Mod<Integer>) result[2], (Integer) values[2]));
		modifyDict(Value2Mod.findMod(getDict(),
				(Map<Integer, Map<Object, Object>>) result[0].apply(values[0])));
		return result;
	}

	public Mod[] synchronize(Mod[] modifications)
			throws SynchronizationFailedException
	{
		Mod[] result;
		result = internalSync(modifications, getKey(), getDict(), getValue());

		setKey(inPlaceApply((Mod<Integer>) result[2], getKey()));
		modifyDict(result[0]);
		return result;
	}

	private Object getValue()
	{
		Map<Object, Object> obj = getDict().get(getKey());
		if (obj == null)
			return null;
		else
			return obj.get(_attributeName);
	}

	private static final DictMod NULLDICTMOD = new DictMod(new HashMap(0));

	private Mod[] internalSync(Mod[] modifications, final Integer key,
			Map<Integer, Map<Object, Object>> dict, Object value)
			throws SynchronizationFailedException
	{
		if (false == modifications[0] instanceof NullMod
				&& false == modifications[0] instanceof DictMod)
			throw new SynchronizationFailedException();

		if (false == modifications[2] instanceof PrimMod
				&& false == modifications[2] instanceof NullMod)
		{
			throw new SynchronizationFailedException();
		}

		if (modifications[2] instanceof NullMod)
		{
			Object newValue = modifications[1].apply(value);
			if (newValue == null) throw new SynchronizationFailedException();

			if (modifications[0] instanceof DictMod || dict != null)
			{

				if (key != null
						&& Helper.areEqual(getAttrValueByKey(modifications[0],
								dict, key), newValue))
				{
					return changeDictAndValueWithFixedKey(modifications, key,
							dict, value);
				}
				
				Iterable<Integer> keySet = dict.keySet();
				if (modifications[0] instanceof DictMod) {
					keySet = new TwoIterable<Integer>(dict.keySet(), ((DictMod)modifications[0]).getKeys());
				}
				
				for (Integer k : keySet)
				{

					if (Helper.areEqual(getAttrValueByKey(modifications[0],
							dict, k), newValue))
					{
						return changeDictAndValueWithFixedKey(new Mod[] {
								modifications[0], modifications[1],
								new PrimMod<Integer>(k) }, k, dict, value);
					}
				}
			}

			Integer newKey = new GlobalIncrementalFactory().createNewKey();
			return changeDictAndValueWithFixedKey(new Mod[] { modifications[0],
					modifications[1], new PrimMod<Integer>(newKey) }, newKey,
					dict, value);
		}
		else
			return changeDictAndValueWithFixedKey(modifications, key, dict,
					value);

	}

	private Object getAttrValueByKey(Mod dictMod,
			Map<Integer, Map<Object, Object>> dict, Integer key)
			throws ModTypeUnsupportedException
	{
		Mod objMod = getInnerMod(dictMod, key);
		Mod attrMod = getInnerMod(objMod, _attributeName);

		Map<Object, Object> obj = getInner(dict, key);
		Object value = getInner(obj, _attributeName);

		return attrMod.apply(value);
	}

	private <T, K> T getInner(Map<K, T> dict, K key)
	{
		T obj;
		if (dict != null)
			obj = dict.get(key);
		else
			obj = null;
		return obj;
	}

	private Mod getInnerMod(Mod dictMod, Object key)
			throws ModTypeUnsupportedException
	{
		Mod objMod;
		if (dictMod instanceof NullMod)
			objMod = NullMod.INSTANCE;
		else if (dictMod instanceof DictMod)
			objMod = ((DictMod) dictMod).getMod(key);
		else
			throw new ModTypeUnsupportedException();
		return objMod;
	}

	private Mod[] changeDictAndValueWithFixedKey(Mod[] modifications,
			Integer key, Map<Integer, Map<Object, Object>> dict, Object value)
			throws SynchronizationFailedException
	{
		key = (Integer) modifications[2].apply(key);
		if (key == null) throw new SynchronizationFailedException();

		Mod objMod = getInnerMod(modifications[0], key);
		Mod mod1 = getInnerMod(objMod, _attributeName);
		if (conflict(mod1, modifications[1])) throw new SynchronizationFailedException();

		Object obj = getInner(getInner(dict, key), _attributeName);
		Mod vresult = IDSync.mergeWithValue(mod1, modifications[1], obj, value, true);

		if (vresult.equals(NULLDICTMOD)) vresult = NullMod.INSTANCE;

		Mod dresult = DictMod.replaceNullable(modifications[0], key, 
				DictMod.replaceNullable(objMod, _attributeName, vresult));
//		if (dresult.equals(NULLDICTMOD)
//				&& (modifications[0] instanceof NullMod) && dict != null)
//			dresult = NullMod.INSTANCE;

		Mod kresult;
		if (dresult.equals(modifications[0])
				&& vresult.equals(modifications[1]))
			kresult = modifications[2];
		else
		{
			kresult = new PrimMod<Integer>(key);
			assert !kresult.isConflict(modifications[2]);
		}

		Mod[] result = new Mod[] { dresult, vresult, kresult };
		return result;
	}

	//	@Override
	//	public boolean isModConcerned(int index, Mod mod)
	//			throws ModTypeUnsupportedException
	//	{
	//		if (!super.isModConcerned(index, mod)) return false;
	//		if (index == 0)
	//		{
	//			try
	//			{
	//				Mod objMod = ((DictMod) mod).getMod(getKey());
	//				if (objMod == null || objMod instanceof NullMod) return false;
	//				Mod attrMod = ((DictMod) objMod).getMod(getKey());
	//				return attrMod != null && !(attrMod instanceof NullMod);
	//			}
	//			catch (ClassCastException e)
	//			{
	//				throw new ModTypeUnsupportedException();
	//			}
	//		}
	//
	//		return true;
	//	}
}
